<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <title></title>
    <!-- 引入 Brython -->
    <script src="https://cdn.jsdelivr.net/npm/brython@3.10.5/brython.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/brython@3.10.5/brython_stdlib.js"></script>
    <style>
        body {
            margin: 0;
            overflow: hidden;
            background: #000;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            font-family: "Helvetica", "Arial", sans-serif;
            color: #FF99CC;
        }
        canvas {
            display: block;
        }
        .text {
            position: absolute;
            font-size: 24px;
            font-weight: bold;
            pointer-events: none;
            text-align: center;
            width: 100%;
        }
    </style>
</head>
<body onload="brython()">
    <canvas id="canvas" width="640" height="480"></canvas>
    <div class="text">刘亚楠, 我喜欢你</div>

    <script type="text/python">
        from browser import document, window, timer
        import math, random

        # ================== 参数设置 ==================
        CANVAS_WIDTH = 640
        CANVAS_HEIGHT = 480
        CANVAS_CENTER_X = CANVAS_WIDTH / 2
        CANVAS_CENTER_Y = CANVAS_HEIGHT / 2
        IMAGE_ENLARGE = 11
        HEART_COLOR = "#FF99CC"

        # ------------------ 函数定义 ------------------

        def heart_function(t, shrink_ratio=IMAGE_ENLARGE):
            x = 16 * (math.sin(t) ** 3)
            y = -(13 * math.cos(t) - 5 * math.cos(2*t) - 2 * math.cos(3*t) - math.cos(4*t))
            x *= shrink_ratio
            y *= shrink_ratio
            x += CANVAS_CENTER_X
            y += CANVAS_CENTER_Y
            return int(x), int(y)

        def scatter_inside(x, y, beta=0.15):
            ratio_x = -beta * math.log(random.random())
            ratio_y = -beta * math.log(random.random())
            dx = ratio_x * (x - CANVAS_CENTER_X)
            dy = ratio_y * (y - CANVAS_CENTER_Y)
            return x - dx, y - dy

        def shrink(x, y, ratio):
            dist_sq = (x - CANVAS_CENTER_X)**2 + (y - CANVAS_CENTER_Y)**2
            if dist_sq == 0:
                return x, y
            force = -1 / (dist_sq ** 0.6)
            dx = ratio * force * (x - CANVAS_CENTER_X)
            dy = ratio * force * (y - CANVAS_CENTER_Y)
            return x - dx, y - dy

        def curve(p):
            return 2 * (2 * math.sin(4 * p)) / (2 * math.pi)

        # ------------------ Heart 类 ------------------
        class Heart:
            def __init__(self, generate_frame=20):
                self._points = set()
                self._edge_diffusion_points = set()
                self._center_diffusion_points = set()
                self.all_points = {}
                self.build(2000)
                self.generate_frame = generate_frame
                for frame in range(generate_frame):
                    self.calc(frame)

            def build(self, number):
                # 基础心形点
                for _ in range(number):
                    t = random.uniform(0, 2 * math.pi)
                    x, y = heart_function(t)
                    self._points.add((x, y))

                # 边缘扩散点
                for _x, _y in list(self._points):
                    for _ in range(3):
                        x, y = scatter_inside(_x, _y, 0.05)
                        self._edge_diffusion_points.add((x, y))

                # 中心扩散点
                point_list = list(self._points)
                for _ in range(4000):
                    x, y = random.choice(point_list)
                    x, y = scatter_inside(x, y, 0.17)
                    self._center_diffusion_points.add((x, y))

            @staticmethod
            def calc_position(x, y, ratio):
                dist_sq = (x - CANVAS_CENTER_X)**2 + (y - CANVAS_CENTER_Y)**2
                if dist_sq == 0:
                    return x, y
                force = 1 / (dist_sq ** 0.520)
                dx = ratio * force * (x - CANVAS_CENTER_X) + random.randint(-1, 1)
                dy = ratio * force * (y - CANVAS_CENTER_Y) + random.randint(-1, 1)
                return x - dx, y - dy

            def calc(self, generate_frame):
                ratio = 10 * curve(generate_frame / 10 * math.pi)
                halo_radius = int(4 + 6 * (1 + curve(generate_frame / 10 * math.pi)))
                halo_number = int(3000 + 4000 * abs(curve(generate_frame / 10 * math.pi) ** 2))
                all_points = []
                heart_halo_point = set()

                # 光晕点
                for _ in range(halo_number):
                    t = random.uniform(0, 2 * math.pi)
                    x, y = heart_function(t, shrink_ratio=11.6)
                    x, y = shrink(x, y, halo_radius)
                    if (x, y) not in heart_halo_point:
                        heart_halo_point.add((x, y))
                        x += random.randint(-14, 14)
                        y += random.randint(-14, 14)
                        size = random.choice([1, 2, 2])
                        all_points.append((x, y, size))

                # 主体点
                for x, y in self._points:
                    nx, ny = self.calc_position(x, y, ratio)
                    size = random.randint(1, 3)
                    all_points.append((nx, ny, size))

                # 边缘扩散点
                for x, y in self._edge_diffusion_points:
                    nx, ny = self.calc_position(x, y, ratio)
                    size = random.randint(1, 2)
                    all_points.append((nx, ny, size))

                # 中心扩散点
                for x, y in self._center_diffusion_points:
                    nx, ny = self.calc_position(x, y, ratio)
                    size = random.randint(1, 2)
                    all_points.append((nx, ny, size))

                self.all_points[generate_frame] = all_points

            def render(self, ctx, render_frame):
                points = self.all_points.get(render_frame % self.generate_frame, [])
                for x, y, size in points:
                    ctx.fillStyle = HEART_COLOR
                    ctx.fillRect(x, y, size, size)

        # ================== 主程序 ==================
        canvas = document["canvas"]
        ctx = canvas.getContext("2d")
        heart = Heart(generate_frame=20)
        frame = 0

        def animate():
            global frame
            ctx.fillStyle = "black"
            ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT)
            heart.render(ctx, frame)
            frame += 1
            timer.request_animation_frame(animate)

        # 启动动画
        animate()
    </script>
</body>
</html>